from waflib import Logs, Options, Utils, Configure


class CompilerTraits(object):
	def get_compiler_flags(self):
		"""get_compiler_flags() -> list of cflags"""
		raise NotImplementedError

	def get_warnings_flags(self, level):
		"""get_warnings_flags(level) -> list of cflags"""
		raise NotImplementedError

	def get_optimization_flags(self, level):
		"""get_optimization_flags(level) -> list of cflags"""
		raise NotImplementedError

	def get_debug_flags(self, level):
		"""get_debug_flags(level) -> (list of cflags, list of cppdefines)"""
		raise NotImplementedError


class GccTraits(CompilerTraits):
	def __init__(self):
		super(GccTraits, self).__init__()
		# cumulative list of warnings per level
		self.warnings_flags = [['-Wall', '-Wno-error=deprecated-declarations',
                                        '-fstrict-aliasing', '-Wstrict-aliasing']]

	def get_compiler_flags(self):
		return ['-std=c++14']

	def get_warnings_flags(self, level):
		warnings = []
		for l in range(level):
			if l < len(self.warnings_flags):
				warnings.extend(self.warnings_flags[l])
			else:
				break
		return warnings

	def get_optimization_flags(self, level):
		if level == 0:
			return ['-O0']
		elif level == 1:
			return ['-O']
		elif level == 2:
			return ['-O2']
		elif level == 3:
			return ['-O3']

	def get_debug_flags(self, level):
		if level == 0:
			return (['-g0'], ['NDEBUG'])
		elif level == 1:
			return (['-g'], [])
		elif level >= 2:
			return (['-ggdb', '-g3'], ['_DEBUG'])
		

class IccTraits(CompilerTraits):
	def __init__(self):
		super(IccTraits, self).__init__()
		# cumulative list of warnings per level
		# icc is _very_ verbose with -Wall, -Werror is barely achievable
		self.warnings_flags = [[], [], ['-Wall']]

	def get_compiler_flags(self):
		return ['/Qstd=c++14']

	def get_warnings_flags(self, level):
		warnings = []
		for l in range(level):
			if l < len(self.warnings_flags):
				warnings.extend(self.warnings_flags[l])
			else:
				break
		return warnings

	def get_optimization_flags(self, level):
		if level == 0:
			return ['-O0']
		elif level == 1:
			return ['-O']
		elif level == 2:
			return ['-O2']
		elif level == 3:
			return ['-O3']

	def get_debug_flags(self, level):
		if level == 0:
			return (['-g0'], ['NDEBUG'])
		elif level == 1:
			return (['-g'], [])
		elif level >= 2:
			return (['-ggdb', '-g3'], ['_DEBUG'])
		

class MsvcTraits(CompilerTraits):
	def __init__(self):
		super(MsvcTraits, self).__init__()
		# cumulative list of warnings per level
		self.warnings_flags = [['/W2'], ['/WX'], ['/Wall']]

	def get_compiler_flags(self):
		return []

	def get_warnings_flags(self, level):
		warnings = []
		for l in range(level):
			if l < len(self.warnings_flags):
				warnings.extend(self.warnings_flags[l])
			else:
				break
		return warnings

	def get_optimization_flags(self, level):
		if level == 0:
			return ['/Od']
		elif level == 1:
			return []
		elif level == 2:
			return ['/O2']
		elif level == 3:
			return ['/Ox']

	def get_debug_flags(self, level):
		if level == 0:
			return ([], ['NDEBUG'])
		elif level == 1:
			return (['/ZI', '/RTC1'], [])
		elif level >= 2:
			return (['/ZI', '/RTC1'], ['_DEBUG'])



gcc = GccTraits()
icc = IccTraits()
msvc = MsvcTraits()

# how to map env['COMPILER_CC'] or env['COMPILER_CXX'] into a traits object
compiler_mapping = {
	'gcc': gcc,
	'g++': gcc,
	'msvc': msvc,
	'icc': icc,
	'icpc': icc,
	'clang': gcc,
	'clang++': gcc,
}

profiles = {
	# profile name: [optimization_level, warnings_level, debug_level]
	'default': [2, 1, 1],
	'debug': [0, 2, 3],
	'release': [3, 1, 0],
	}

default_profile = 'default'

def options(opt):
	assert default_profile in profiles
	opt.add_option('-d', '--build-profile',
		       action='store',
		       default=default_profile,
		       help=("Specify the build profile.  "
			     "Build profiles control the default compilation flags"
			     " used for C/C++ programs, if CCFLAGS/CXXFLAGS are not"
			     " set in the environment. [Allowed Values: %s]"
			     % ", ".join([repr(p) for p in list(profiles.keys())])),
		       choices=list(profiles.keys()),
		       dest='build_profile')
	opt.add_option('--check-profile',
		       help=('print out current build profile'),
		       default=False, dest='check_profile', action="store_true")
	opt.add_option('--disable-werror',
		       help=('disable -Werror flag (warnings treated as errors'),
		       default=False, dest='disable_werror', action="store_true")
def configure(conf):
	cc = conf.env['COMPILER_CC'] or None
	cxx = conf.env['COMPILER_CXX'] or None
	if not (cc or cxx):
		raise Utils.WafError("neither COMPILER_CC nor COMPILER_CXX are defined; "
				     "maybe the compiler_cc or compiler_cxx tool has not been configured yet?")
	
	try:
		compiler = compiler_mapping[cc]
	except KeyError:
		try:
			compiler = compiler_mapping[cxx]
		except KeyError:
			Logs.warn("No compiler flags support for compiler %r or %r"
				  % (cc, cxx))
			return

	opt_level, warn_level, dbg_level = profiles[Options.options.build_profile]

	compilerFlags = conf.get_supported_cxxflags(compiler.get_compiler_flags(), msg="compiler ")
	optimizations = conf.get_supported_cxxflags(compiler.get_optimization_flags(opt_level), msg="optimizations ")
	debug, debug_defs = compiler.get_debug_flags(dbg_level)
	debug = conf.get_supported_cxxflags(debug, msg="debug ")
	warnings = conf.get_supported_cxxflags(compiler.get_warnings_flags(warn_level), msg="warnings ")

	if Options.options.disable_werror:
		try:
			warnings.remove ('-Werror')
		except ValueError:
			pass

	if cc and not conf.env['CCFLAGS']:
		conf.env.append_value('CCFLAGS', optimizations)
		conf.env.append_value('CCFLAGS', debug)
		conf.env.append_value('CCFLAGS', warnings)
		conf.env.append_value('CCDEFINES', debug_defs)
	if cxx and not conf.env['CXXFLAGS']:
		conf.env.append_value('CXXFLAGS', compilerFlags)
		conf.env.append_value('CXXFLAGS', optimizations)
		conf.env.append_value('CXXFLAGS', debug)
		conf.env.append_value('CXXFLAGS', warnings)
		conf.env.append_value('CXXDEFINES', debug_defs)

@Configure.conf
def get_supported_cxxflags(self, cxxflags, msg=""):
	"""
	Check which cxxflags are supported by the compiler
	"""
	self.start_msg('Checking supported %sCXXFLAGS' % msg)

	supportedFlags = []
	for flag in cxxflags:
		if self.check_cxx(cxxflags=['-Werror', flag], mandatory=False):
			supportedFlags += [flag]

	self.end_msg(' '.join(supportedFlags))
	return supportedFlags
